import { DisconnectReason, Server, Socket } from "socket.io"
import { ISocketData } from '../data/SocketData'

export type uid = string
export type ConnectCallbackFn = (uid: uid, socket: Socket) => void
export type DisconnectCallbackFn = (reason: DisconnectReason, description?: any) => void

/**
 * 客户端socket对象管理类
 * 根据用户id保存对应的socket对象，方便http和socket进行关系对应
 * 注意：用户连接时必须在auth中携带用户uid才可以socket连接
 */
export default class SocketManager {
  private clientSockets: Map<uid, Socket>                 // 根据用户id保存用户socket对象
  private connectCallbacks: Set<ConnectCallbackFn>        // 连接回调函数集合

  private _io: Server = null!

  // private static _instance: SocketManager = null!

  // socket管理器单例
  // public static get instance() {
  //   if (this._instance === null) {
  //     this._instance = new SocketManager()
  //   }
  //   return this._instance
  // }

  // socket.io对象
  public get io() {
    return this._io
  }

  protected constructor() {
    this.clientSockets = new Map()
    this.connectCallbacks = new Set()
  }

  /**
   * 初始化
   * @param io 
   */
  public init(io: Server) {
    this._io = io
    io.on('connection', this.onConnect.bind(this))
  }

  /**
   * 客户端连接回调
   * @param socket 
   */
  private onConnect(socket: Socket): void {
    // 获取用户id
    const { uid } = socket.handshake.auth
    // 无用户id，则不需进行下面的步骤（无效登录连接）
    // token校验...
    if (uid == null) {
      socket.disconnect()
      return
    }
    console.log('[socket] client connected, its uid is:', uid, socket.id)
    // 保存socket
    this.clientSockets.set(uid, socket)
    // 通知监听事件
    this.connectCallbacks.forEach(fn => { fn(uid, socket) })
    // 监听离开
    socket.on('disconnect', this.onDisconnect(uid, socket))
  }

  /**
   * 客户端离开回调
   * @param socket 
   */
  private onDisconnect(uid: uid, socket: Socket) {
    return (reason: DisconnectReason, description?: any) => {
      console.log('[socket] client disconnect, its uid is:', uid, socket.id)
      // 删除socket
      this.clientSockets.delete(uid)
    }
  }

  /**
   * 添加连接监听器
   * @param callback 有人连接后的回调函数 
   */
  public addConnectListener(callback: ConnectCallbackFn) {
    this.connectCallbacks.add(callback)
  }

  /**
   * 添加连接监听器
   * @param callback 有人连接后的回调函数 
   */
  public addDisconnectListener(uid: string, callback: DisconnectCallbackFn) {
    const socket = this.getClientSocket(uid)
    if (socket) {
      socket.on('disconnect', callback)
    }
  }

  /**
   * 设置socket对象加入某个房间
   */
  public intoRoom(uid: string, roomId: string) {
    const socket = this.clientSockets.get(uid)
    if (socket == undefined) {
      console.log(`[socket] uid:${uid} of 'socket object' does not exist`)
      return
    }
    socket.join(roomId)

  }

  /**
   * socket对象离开某个房间
   * @param uid 
   * @param roomId 
   * @returns 
   */
  public leaveRoom(uid: string, roomId: string) {
    const socket = this.clientSockets.get(uid)
    if (socket == undefined) {
      console.log(`[socket] uid:${uid} of 'socket object' does not exist`)
      return
    }
    socket.leave(roomId)
  }

  /**
   * 发送数据
   * @param event 事件
   * @param uid 目标id
   * @param data 
   */
  public send(uid: string, data: ISocketData) {
    const socket = this.clientSockets.get(uid)
    if (socket == undefined) {
      console.log(`[socket] uid:${uid} of 'socket object' does not exist`)
      return
    }

    console.log('[socket] send data:', data)
    socket.emit(data.event, data)
  }

  /**
   * 发送数据到用户所在房间（该用户不会收到通知）
   * @param uid 
   * @param roomId 
   * @param data 
   */
  public sendToRoom(uid: string, roomId: string, data: ISocketData) {
    const socket = this.clientSockets.get(uid)
    if (socket == undefined) {
      console.log(`[socket] uid:${uid} of 'socket object' does not exist`)
      return
    }

    console.log('[socket] send data:', data)
    socket.to(roomId).emit(data.event, data)
  }

  /**
   * 发送数据到房间的所有人
   * @param roomId 
   * @param data 
   */
  public sendToRoomAll(roomId: string, data: ISocketData) {
    console.log('[socket] send data:', data)
    this._io.to(roomId).emit(data.event, data)
  }

  /**
   * 获取用户id对应的socket对象
   * @param uid 
   * @returns 
   */
  public getClientSocket(uid: uid): Socket | undefined {
    return this.clientSockets.get(uid)
  }
}